<!DOCTYPE html>
<html lang="zh-cn">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        html {
            background: #fff;
        }

        body {
            width: 100%;
            max-width: 2560px;
            margin: 0 auto;
            background: #ccc;
        }

        canvas {
            object-fit: cover;
            margin: 0 auto;
        }
    </style>

    <style>
        #wrapper {
            position: relative;
            /* text-align: center; */
            overflow: hidden;
        }

        /* #shelter {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: #00000011;
        } */

        #banner {
            filter: contrast(105%);
        }
    </style>
</head>

<body>

    <div id="wrapper">
        <div id="shelter"></div>
        <canvas id="banner"></canvas>
    </div>

    <div>
        <span>实时帧率：</span> <span id="fps"></span>
    </div>
    <div>
        <span>平均帧率：</span> <span id="afps"></span>
    </div>
    <div>
        <span>间隔帧率：</span> <span id="ifps"></span>
    </div>
    <script>
        // 动画帧率
        const canvasFPS = 240,
            // 恢复量倍率
            recoverySpeed = 0.86,
            // 区域加载间隔
            loadInterval = 110,
            // 锐化程度
            sharpness = 0,
            // 显示帧率的时间间隔
            showFPS_timeout = 1000
        let bannerScale = 1

        const wrapper = document.querySelector('#wrapper')

        // 画布
        const canvasElem = document.querySelector('#banner')

        // 缓存画布，双缓存解决高频刷新时的画面闪烁问题
        const tempCanvas = document.createElement('canvas')

        // 锐化画布
        const sharpenCanvas = document.createElement('canvas')



        const ctx = canvasElem.getContext('2d')
        const tempCtx = tempCanvas.getContext('2d')
        const sharpenCtx = sharpenCanvas.getContext('2d')


        // 设置画布大小
        // 依据：b站主页缩放至50%时，Banner显示的最大值
        const canvasSize = {
            w: 2560,
            h: 240
        }

        canvasElem.width = canvasSize.w, canvasElem.height = canvasSize.h
        tempCanvas.width = canvasSize.w, tempCanvas.height = canvasSize.h
        sharpenCanvas.width = canvasSize.w, sharpenCanvas.height = canvasSize.h


        // 锐化程度
        sharpenCtx.globalAlpha = sharpness

        // 设置填充色
        ctx.fillStyle = '#7bc6fc';
        // 填充整个 Canvas 画布
        ctx.fillRect(0, 0, canvasSize.w, canvasSize.h);

        // 视频
        // 309.677px 774.194px
        // 167.226px 418.065px
        // 200 500


        const bubble1 = videoNode('./src/528d9376-f0fd-4445-9840-13e7559d6056.webm', {
            playbackRate: 1.5
        })
        const bubble2 = videoNode('./src/528d9376-f0fd-4445-9840-13e7559d6056.webm')

        const bubble3 = videoNode('./src/1671f963-c10d-4558-af99-e20a26c5a1f8.webm', {
            playbackRate: 1.5
        })

        // 页面大小变更时触发
        function winResize() {
            const gw = document.documentElement.clientWidth
            // 
            if (gw < canvasSize.w) {
                bannerScale = gw / canvasSize.w
                // tempCanvas.imageSmoothingQuality = 'high'
                // tempCanvas.lineWidth = 1
                if (bannerScale < 0.3) {
                    bannerScale = 0.565
                } else if (bannerScale < 0.6) {
                    bannerScale += 0.11
                }
            } else {
                bannerScale = 1
            }

            // console.log(document.documentElement.clientWidth / canvasSize.w);



            const w = bannerScale * canvasSize.w,
                h = bannerScale * canvasSize.h

            const widthOffset = gw - w

            if (widthOffset < 0) {
                wrapper.style.marginLeft = widthOffset / 2 + 'px'
            } else {
                wrapper.style.margin = '0 auto'
            }

            // console.log('浏览器内缩放', window.devicePixelRatio);


            // console.log(bannerScale);

            // 画布大小更改
            canvasElem.width = w
            canvasElem.height = h

            wrapper.style.width = w + 'px'
            // wrapper.style.height = h + 'px'

            // canvasElem.style.marginLeft = 100 + 'px'




            // canvasElem.style.marginLeft = '12.5%'
            // wrapper.style.marginLeft = (document.body.clientWidth - canvasElem.width) / 2 + 'px'

        }

        fetch('./src/base.json')
            .then(res => res.text())
            .then(data => {
                // console.log(JSON.parse(data));

                draw(JSON.parse(data), loadInterval)
            })


        const imgList = []


        /*  */
        let origin = null, offset = 0
        const offsetCalc = (weight = 0) => {
            return offset * 0.1 * weight
        }

        let startTime, endTime, avgFPS, intervalFPS_startTime = Date.now(), intervalFPS_set = [],
        // yids
            canvasScrolling = false

        function draw(imgSet, duration = null) {
            startTime = performance.now()


            tempCtx.fillStyle = '#7bc6fc'
            tempCtx.fillRect(0, 0, canvasElem.width, canvasElem.height)

            // tempCtx.clearRect(0, 0, canvasElem.width, canvasElem.height)
            // tempCtx.save()


            if (duration == Number(duration)) {
                let i = 0
                for (let k in imgSet) {
                    imgList[i] = new Image()
                    imgList[i].src = imgSet[k]
                    imgList[i].id = k

                    // 偏移权重
                    // const offsetWeight = Math.random()
                    let offsetWeight = 0, offsetTopWeight = 0, scaleWeight = 0, scaleFactor = 0.8, left = 0, top = 0

                    const _init = () => {
                        imgList[i].offsetWeight = offsetWeight
                        imgList[i].offsetTopWeight = offsetTopWeight
                        imgList[i].scaleWeight = scaleWeight
                        imgList[i].scaleFactor = scaleFactor
                        imgList[i].left = left
                        imgList[i].top = top
                    }


                    switch (k) {
                        case '198efffbc58493300854c04ab0ea8d979a6f9223.png@1c.webp':
                            // 屏幕
                            scaleFactor = 0.81
                            left = -30
                            offsetWeight = 0.88
                            break;
                        case 'ce8c5e45230a6d3805baf60f5916f1cd441aac8e.png@1c.webp':
                            // 33
                            /*  */
                            offsetWeight = 0.4
                            left = -30
                            scaleFactor = 0.81
                            break;
                        case 'cbf19f3682dfb02e62557d07fefaf241a80296a1.png@1c.webp':
                            // 路人
                            offsetWeight = 0.56

                            break;
                        case '8ea0e95a8e5fc85ae227810925dba1ace1e9fcba.png@1c.webp':
                            // 拉行李的路人
                            offsetWeight = 0.36
                            left = -20


                            _init()

                            // 插入泡泡3（被植物覆盖
                            i++
                            imgList[i] = bubble3
                            left = 1980
                            scaleFactor = 0.77
                            imgList[i].id = '1671f963-c10d-4558-af99-e20a26c5a1f8.webm'
                            break;
                        case '7998ca9f0bc267375fb7b45f75626d96806f94d7.png@1c.webp':
                            // 植物 13
                            offsetWeight = 0.9

                            // console.log(imgList[i-1].id);

                            break;
                        case 'd6c941cf2d5fc6c717173f7e3f166dbc444aa15b.png@1c.webp':
                            // 两边背景建筑
                            offsetWeight = 0.06
                            break;
                        case '782d55aeca6cc75f51d2d630005f514a61a0ddfa.png@1c.webp':
                            // 中间背景建筑
                            offsetWeight = 0.07
                            break;
                        case '42485baddbca05d2c4c7710a0b76b74d303e06d7.png@1c.webp':
                            // 22看窗
                            /*  */
                            offsetWeight = 0.26
                            scaleFactor = 0.835
                            left = -72
                            top = -6
                            break;

                        case '09d0855b6b6d6965e8f02404777986237848c6c9.png@1c.webp':
                            // 大鲸鱼
                            offsetWeight = 0.96
                            offsetTopWeight = 0.03
                            left = -16
                            break;

                        case '27e411d92729604aa594858beb5130ed60aad76d.png@1c.webp':
                            // 小鲸鱼
                            offsetWeight = -1.32
                            offsetTopWeight = -0.13
                            left = -34
                            top = -2

                            scaleWeight = 0.001
                            break;

                        case 'cd68251cde11936871237ca94360acb451bf7ed2.png@1c.webp':
                            // 光 最后一张图片
                            left = -180
                            offsetWeight = 1.58
                            _init()

                            // 插入泡泡1
                            i++
                            imgList[i] = bubble1
                            left = 71
                            scaleFactor = 0.8
                            offsetWeight = 0
                            _init()

                            // 插入泡泡2
                            i++
                            imgList[i] = bubble2
                            left = 1489
                            scaleFactor = 1.5
                            offsetWeight = 0

                            break;
                        case '40878bbef514e2d4bf5d660fe1145c869567bec2.png@1c.webp':
                            // 窗上的泡泡
                            left = -15
                            offsetWeight = 0.07
                            break;


                        // 斜向

                        case 'bb6266e1525a51f7920fc8881e47cadeee271b0c.png@1c.webp':
                            // 横滚泡泡
                            offsetWeight = 1.08
                            offsetTopWeight = 0.07
                            left = 142
                            top = -2
                            break;
                        case 'a43c6833d262301373234ffbd6934559d2ce7fb2.png@1c.webp':
                            // 大泡泡
                            offsetWeight = 0.32
                            offsetTopWeight = 0.15
                            left = -14

                            break;
                        case '142a486b8dd500a626a60b68ad993af8dabc8b55.png@1c.webp':
                            // 很多泡泡
                            offsetWeight = 0.91
                            offsetTopWeight = 0.44
                            break;
                        case 'c13ca9c6405c71bf864ed2bc421680cb437f45ef.png@1c.webp':
                            // 小泡泡
                            offsetWeight = 0.2
                            offsetTopWeight = 0.12
                            break;
                        // case 'd6c941cf2d5fc6c717173f7e3f166dbc444aa15b.png@1c.webp':
                        //     offsetWeight = 0.05
                        //     break;

                        default:
                            break;
                    }

                    const loadImg = index => {
                        // 区域间隔加载
                        setTimeout(() => {
                            switch (imgList[index].id) {
                                case 'cd68251cde11936871237ca94360acb451bf7ed2.png@1c.webp':
                                    tempCtx.globalAlpha = 0.5
                                    break;

                                default:
                                    tempCtx.globalAlpha = 1
                                    ctx.globalAlpha = index / imgList.length
                                    break;
                            }

                            tempCtx.drawImage(imgList[index],
                                left, top,
                                imgList[index].width * scaleFactor * bannerScale,
                                imgList[index].height * scaleFactor * bannerScale)




                            // if (sharpness) {
                            //     sharpenCtx.clearRect(0, 0, canvasSize.w, canvasSize.h)
                            //     sharpenCtx.drawImage(tempCanvas, 0, 0)
                            //     applySharpen(sharpenCtx, canvasSize.w, canvasSize.h)
                            //     tempCtx.drawImage(sharpenCanvas, 0, 0)
                            // }

                            ctx.drawImage(tempCanvas, 0, 0)



                            endTime = performance.now()
                            const crtFPS = (1000 / (endTime - startTime)).toFixed(2)
                            fps.innerText = crtFPS
                        }, duration * i)
                    }

                    _init()
                    loadImg(i)
                    i++
                }


                setTimeout(() => draw(), 1000 / canvasFPS + (duration * imgList.length))
            } else {
                imgList.forEach((img, i) => {
                    switch (img.id) {
                        case 'cd68251cde11936871237ca94360acb451bf7ed2.png@1c.webp':
                            tempCtx.globalAlpha = 0.5
                            break;

                        default:
                            tempCtx.globalAlpha = 1
                            break;
                    }

                    // 复原动画
                    let left = offsetCalc(img.offsetWeight), top = offsetCalc(img.offsetTopWeight ?? 0), scale = offsetCalc(img.scaleWeight)

                    if (img.latest && origin != Number(origin)) {

                        const trans = val => {
                            const recovery = val * recoverySpeed
                            // 根据帧率变化的偏移补偿
                            return recovery + (recovery * (24 > canvasFPS ? -canvasFPS / 24 : canvasFPS == 24 ? 0 : 24 / canvasFPS))
                        }
                        // 这里存在的精度问题暂时就不考虑了
                        left = trans(img.latest.l)
                        img.latest.l = left

                        top = trans(img.latest.t)
                        img.latest.t = top

                        scale = trans(img.latest.s)
                        img.latest.s = scale
                    } else {
                        img.latest = {
                            l: offsetCalc(img.offsetWeight),
                            t: offsetCalc(img.offsetTopWeight ?? 0),
                            s: offsetCalc(img.scaleWeight)
                        }
                    }

                    const w = (img.width == 0 ? img.videoWidth : img.width),
                        h = (img.height == 0 ? img.videoHeight : img.height)
                    tempCtx.drawImage(img,
                        img.left + left,
                        img.top + top,
                        (w * img.scaleFactor + w * scale) * bannerScale,
                        (h * img.scaleFactor + h * scale) * bannerScale)

                    if (i >= imgList.length - 1) {
                        tempCtx.globalAlpha = 1

                        // 2560 / 2 - 1083.87 - 250 / 2
                        // tempCtx.drawImage(bubble1, 71, 0, bubble1.videoWidth * 0.8, bubble1.videoHeight * 0.8)
                        // // 2560 / 2 + 334.452 - 250 / 2
                        // tempCtx.drawImage(bubble2, 1489, 0, bubble2.videoWidth * 1.5, bubble2.videoHeight * 1.5)
                        // height: 387.097px; width: 154.839px
                        // 2560 / 2 + 774.194 - 250 / 2
                        // tempCtx.drawImage(bubble3, 1929, 0, bubble2.videoWidth * 0.77, bubble2.videoHeight * 0.77)

                        // tempCtx.filter = 'blur(1px)'

                        if (sharpness) {
                            sharpenCtx.clearRect(0, 0, canvasSize.w, canvasSize.h)
                            sharpenCtx.drawImage(tempCanvas, 0, 0)
                            applySharpen(sharpenCtx, canvasSize.w, canvasSize.h)
                            tempCtx.drawImage(sharpenCanvas, 0, 0)
                        }

                        // 最终绘制
                        ctx.drawImage(tempCanvas, 0, 0)


                        setTimeout(() => {
                            endTime = performance.now()
                            const crtFPS = 1000 / (endTime - startTime)

                            avgFPS = avgFPS ? (crtFPS + avgFPS) / 2 : crtFPS

                            if (Date.now() - intervalFPS_startTime > showFPS_timeout) {
                                ifps.innerText = (intervalFPS_set.reduce((sum, currentValue) => sum + currentValue, 0) / intervalFPS_set.length).toFixed(2)

                                // 定时器复位
                                intervalFPS_startTime = Date.now()
                                // 清空数组
                                intervalFPS_set.length = 0
                            } else {
                                intervalFPS_set.push(crtFPS)
                            }

                            fps.innerText = crtFPS.toFixed(2)
                            afps.innerText = avgFPS.toFixed(2)
                            draw()
                        }, 1000 / canvasFPS)
                    }
                })

                /* 
                100 * 0.8 * 1000 / 24

                100 * 0.8 * 1000 / 240
                */

                /* for (let k in imgSet) {
                    const img = new Image()
                    img.src = imgSet[k]
                    img.onload = function () {

                        // 缓存
                        // if (k == 'cd68251cde11936871237ca94360acb451bf7ed2.png@1c.webp') {
                        //     tempCtx.globalAlpha = 0.5

                        // } else {
                        //     tempCtx.globalAlpha = 1
                        // }
                        // console.log(k);

                        tempCtx.drawImage(this, 0, 0, img.width * scaleFactor, img.height * scaleFactor)

                        i++
                        if (i >= Object.keys(imgSet).length) {
                            ctx.drawImage(tempCanvas, 0, 0)

                            // 2560 / 2 - 1083.87 - 250 / 2
                            ctx.drawImage(bubble1, 71, 0, bubble1.videoWidth * 0.8, bubble1.videoHeight * 0.8)
                            // 2560 / 2 + 334.452 - 250 / 2
                            ctx.drawImage(bubble2, 1489, 0, bubble2.videoWidth * 1.5, bubble2.videoHeight * 1.5)

                            // setTimeout(() => draw(imgSet), 10000 / 12)
                        }
                    }
                } */
            }
        }

        window.addEventListener('mousemove', _ => {
            // console.log(_.target == canvasElem)

            // 判断鼠标进入判定区域
            if (_.pageY >= canvasElem.offsetTop && _.pageY <= canvasElem.offsetTop + canvasElem.height) {
                // 获取原始点
                if (origin != Number(origin)) {
                    origin = _.pageX
                }

                // 偏移量
                offset = _.pageX - origin



            } else {
                origin = null, offset = 0

            }

            // console.log(origin);

            // console.log(offset);

        })

        winResize()
        window.addEventListener('resize', () => {
            winResize()
        })



        function videoNode(vidSrc, options = {}) {
            const video = document.createElement('video')
            document.body.append(video)

            video.src = vidSrc
            video.muted = true
            video.loop = true
            video.autoplay = true
            video.style.opacity = 0
            video.style.position = 'absolute'

            for (let k in options) {
                video[k] = options[k]
            }

            return video
        }

        function applySharpen(context, width, height) {
            // 获取原始图像数据
            let originalImageData = context.getImageData(0, 0, width, height);
            let originalPixels = originalImageData.data;

            // 创建一个用于存放处理后的图像数据的 ImageData 对象
            let outputImageData = context.createImageData(width, height);
            let outputPixels = outputImageData.data;

            const kernel = [
                0, -1, 0,
                -1, 5, -1,
                0, -1, 0,
            ];

            const kernelSize = Math.sqrt(kernel.length);
            const halfKernelSize = Math.floor(kernelSize / 2);

            for (let y = 0; y < height; y++) {
                for (let x = 0; x < width; x++) {
                    let r = 0, g = 0, b = 0;

                    for (let ky = 0; ky < kernelSize; ky++) {
                        for (let kx = 0; kx < kernelSize; kx++) {
                            // 考虑边缘像素
                            let pixelY = y + ky - halfKernelSize;
                            let pixelX = x + kx - halfKernelSize;

                            if (pixelY < 0 || pixelY >= height || pixelX < 0 || pixelX >= width) continue;

                            // 卷积计算
                            let offset = (pixelY * width + pixelX) * 4;
                            let weight = kernel[ky * kernelSize + kx];

                            r += originalPixels[offset] * weight;
                            g += originalPixels[offset + 1] * weight;
                            b += originalPixels[offset + 2] * weight;
                        }
                    }

                    let destOffset = (y * width + x) * 4;
                    outputPixels[destOffset] = r;
                    outputPixels[destOffset + 1] = g;
                    outputPixels[destOffset + 2] = b;
                    outputPixels[destOffset + 3] = originalPixels[destOffset + 3]; // 保持相同的 alpha 值
                }
            }

            // 将处理后的图像数据绘制回画布
            context.putImageData(outputImageData, 0, 0);
        }
    </script>
</body>

</html>